package ca.uvic.cs.cogz.mapping.ui;

import java.awt.BorderLayout;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

import ca.uvic.cs.cogz.mapping.core.AbstractMapElement;
import ca.uvic.cs.cogz.mapping.core.MappingTypeManager.MappingType;
import ca.uvic.cs.cogz.mapping.ontology.DataLoaderFacade;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.util.ComponentFactory;
import edu.stanford.smi.protegex.prompt.ui.ActionRenderer;

public class CompletedMappingsPane extends JPanel {
	private static final long serialVersionUID = -8619301142160581191L;
	
	private static final String DEFAULT_FILTER_TEXT = "type filter text";
	
	private List<AbstractMapElement> currentVerifiedMappings;
	private JTextField searchTextField;
	
	private JTable table;

	private int[] columnSizes = new int[] { 230, 230 };

	private String[] columnNames = new String[] { "Source", "Target", };

	private String searchText = "";
	
	public CompletedMappingsPane() {
		init();
	}
	
	protected void init() {
		searchTextField = new JTextField(20);
		searchTextField.setText(DEFAULT_FILTER_TEXT);
		searchTextField.addFocusListener(getSearchFocusListener());
		searchTextField.addKeyListener(getSearchKeyAdapter());
		
		initVerifiedMappings();
		
		setLayout(new BorderLayout());
		add(getCompletedMappingsPanel(), BorderLayout.CENTER);
		add(searchTextField, BorderLayout.NORTH);
	}
	
	public void refresh() {
		changeList(getVerifiedMappings());
	}
	
	public JTable getMappingsTable() {
		return table;
	}
	
	public AbstractMapElement getSelectMapElement() {
		int row = table.getSelectedRow();
		if(row >= 0) {
			return  (AbstractMapElement)table.getModel().getValueAt(row, 0);
		}
		
		return null;
	}
	
	private void initVerifiedMappings() {
		currentVerifiedMappings = getVerifiedMappings();
	}
	
	private List<AbstractMapElement> getVerifiedMappings() {
		List<AbstractMapElement> mapElements = DataLoaderFacade.getInstance().getMappingCollection().getMappedElements(MappingType.VERIFIED_MAPPING);
		List<AbstractMapElement> currentVerifiedMappings = new ArrayList<AbstractMapElement>();
		Set<String> seen = new HashSet<String>();
		for(AbstractMapElement mapElement : mapElements) {
			if(!seen.contains(mapElement.toString())) {
				currentVerifiedMappings.add(mapElement);
				seen.add(mapElement.toString());
			}
		}
		
		return currentVerifiedMappings;
	}
	
	private JPanel getCompletedMappingsPanel() {
		JPanel completedMappingsPanel = new JPanel(new BorderLayout());
		createTable();
		
		completedMappingsPanel.add(ComponentFactory.createScrollPane(table), BorderLayout.CENTER);
		
		return completedMappingsPanel;
	}

	private void createTable() {
		table = ComponentFactory.createTable(null);
		table.setModel(createTableModel(columnNames));

		table.getTableHeader().addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				JTableHeader header = table.getTableHeader();
				int column = header.columnAtPoint(e.getPoint());

				((MappingTableModel) table.getModel()).setSortIndex(column);
				table.setColumnSelectionInterval(0, columnNames.length-1);
			}
		});

		table.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				table.setColumnSelectionInterval(0, columnNames.length-1);
			}
		});

		for (int i = 0; i < columnSizes.length; i++) {
			addColumn(columnSizes[i], new FrameRenderer(i));
		}

		table.setColumnSelectionAllowed(true);
		table.setColumnSelectionInterval(0, columnNames.length - 1);
		
		changeList(currentVerifiedMappings);
	}

	private void addColumn(int width, TableCellRenderer renderer) {
		if (table.getColumnCount() < columnNames.length) {
			table.addColumn(new TableColumn(table.getColumnCount(), width, renderer, null));
		}
	}

	private TableModel createTableModel(String[] columnNames) {
		return new MappingTableModel(columnNames);
	}
	
	public void filterOperations(List<AbstractMapElement> itemsToFilter, String searchText) {
		this.searchText = searchText;
		if (itemsToFilter != null) {
			String[] searchTextElements = searchText.toLowerCase().split(" ");
			List<AbstractMapElement> matchingMapElements = new ArrayList<AbstractMapElement>();
			for (String search : searchTextElements) {
				if(search.startsWith("*")) {
					search = search.substring(1);
				}
				for (AbstractMapElement mapElement : itemsToFilter) {
					if(mapElement.getSourceFrame().getBrowserText().toLowerCase().contains(search)
							|| mapElement.getTargetFrame().getBrowserText().toLowerCase().contains(search)) {
						matchingMapElements.add(mapElement);
					}
				}
			}
			changeList(matchingMapElements, false);
		}
	}

	private void changeList(List<AbstractMapElement> actionList, boolean applyFilter) {
		currentVerifiedMappings = actionList;
		if (applyFilter) {
			filterOperations(currentVerifiedMappings, searchText);
			return;
		}

		MappingTableModel tableModel = (MappingTableModel) table.getModel();
		int sortColumn = tableModel.getSortColumn();
		boolean ascending = false;
		if(sortColumn >= 0) ascending = tableModel.ascending[sortColumn];
		
		TableModel model = createTableModel(columnNames);
		table.getSelectionModel().clearSelection();
		table.setModel(model);
		if (model.getRowCount() > 0) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					// call selection performed for this event
					table.getSelectionModel().setSelectionInterval(0, 0);
				}
			});
		}
		
		if(sortColumn >= 0) {
			((MappingTableModel) table.getModel()).ascending[sortColumn] = !ascending;
			((MappingTableModel) table.getModel()).setSortIndex(sortColumn);
		}
		
		table.setColumnSelectionInterval(0, columnNames.length - 1);
	}

	public void changeList(List<AbstractMapElement> actionList) {
		changeList(actionList, true);
	}
	
	private KeyAdapter getSearchKeyAdapter() {
		return new KeyAdapter() {
			public void keyReleased(KeyEvent e) {
				if (searchTextField.getText().length() == 0) {
					changeList(getVerifiedMappings());
				} else {
					filterOperations(getVerifiedMappings(), searchTextField.getText());
				}
			}
		};
	}

	/**
	 * Creates a focus listener for the text boxes to remove the default search text on focus and
	 * add it back in if nothing has been searched and focus has been lost.
	 */
	private FocusListener getSearchFocusListener() {
		return new FocusListener() {
			public void focusGained(FocusEvent e) {
				if (searchTextField.getText().equals(DEFAULT_FILTER_TEXT)) {
					searchTextField.setText("");
				}
			}

			public void focusLost(FocusEvent e) {
				if (searchTextField.getText().length() == 0) {
					searchTextField.setText(DEFAULT_FILTER_TEXT);
				}
			}
		};
	}

	private class FrameRenderer extends ActionRenderer {
		private static final long serialVersionUID = 362492992352277396L;

		int index;

		FrameRenderer(int i) {
			super();
			index = i;
		}

		public void load(Object o) {
			AbstractMapElement mapElement = (AbstractMapElement) o;
			if(index == 0) setArgs(new Object[] { mapElement.getSourceFrame() });
			else setArgs(new Object[] { mapElement.getTargetFrame() });
		}
	}

	/**
	 * Table model for the action items. This supports sorting for each column.
	 * 
	 * @author seanf
	 */
	class MappingTableModel extends DefaultTableModel {
		private static final long serialVersionUID = 4968785418293610804L;

		private int sortColumn = -1;
		private boolean[] ascending;
		private String[] columnNames;

		public MappingTableModel(String[] columnNames) {
			this.columnNames = columnNames;
			for (int c = 0; c < columnNames.length; c++) {
				addColumn(columnNames[c]);
			}
			ascending = new boolean[columnNames.length];
		}
		
		public int getSortColumn() {
			return sortColumn;
		}

		public void setSortIndex(int column) {
			if(column >= 0 && column < columnNames.length) {
				int prevColumn = sortColumn;
				sortColumn = column;
	
				if (prevColumn >= 0) {
					table.getColumnModel().getColumn(prevColumn).setHeaderValue(getColumnName(prevColumn));
				}
	
				ascending[sortColumn] = !ascending[sortColumn];
	
				Collections.sort(currentVerifiedMappings, new Comparator<AbstractMapElement>() {
					public int compare(AbstractMapElement mapElement1, AbstractMapElement mapElement2) {
						if (ascending[sortColumn]) {
							return compareMapElements(mapElement1, mapElement2);
						} else {
							return compareMapElements(mapElement2, mapElement1);
						}
					}
				});
	
				table.getColumnModel().getColumn(sortColumn).setHeaderValue(getColumnName(sortColumn));
	
				fireTableDataChanged();
			}
		}

		public String getColumnName(int column) {
			if (sortColumn == column) {
				return columnNames[column] + " " + ((ascending[column]) ? "(asc)" : "(desc)");
			}
			return columnNames[column];
		}

		public int getRowCount() {
			return currentVerifiedMappings.size();
		}

		public Object getValueAt(int row, int column) {
			AbstractMapElement action = currentVerifiedMappings.get(row);
			return action;
		}

		public boolean isCellEditable(int row, int col) {
			return false;
		}

		private int compareMapElements(AbstractMapElement mapElement1, AbstractMapElement mapElement2) {
			if (sortColumn == 0) {
				Frame frame1 = mapElement1.getSourceFrame();
				Frame frame2 = mapElement2.getSourceFrame();
				return frame1.getBrowserText().compareToIgnoreCase(frame2.getBrowserText());
			} else if (sortColumn == 2) {
				Frame frame1 = mapElement1.getTargetFrame();
				Frame frame2 = mapElement2.getTargetFrame();
				return frame1.getBrowserText().compareToIgnoreCase(frame2.getBrowserText());
			}
			return -1;
		}
	}
}
